﻿//-----------------------------------------------------------------------------
//<filedescription file="CommandLineWrapper.cs" company="Microsoft">
//  <copyright>
//     Copyright © Microsoft Corporation.  All rights reserved.
//      ***  DESIGNATED SAMPLE CODE (per the included software license terms) ***
//     ***  DESIGNATED SAMPLE CODE (per the included software license terms) ***
//      THIS CODE SAMPLE IS MADE AVAILABLE TO YOU WITHOUT WARRANTY OF ANY KIND
//      AND CONFERS NO RIGHTS ON YOU WHATSOEVER EXCEPT THE RIGHT TO REVIEW IT
//      SOLELY AS A REFERENCE.  THE ENTIRE RISK OF USE OR RESULTS FROM USE OF
//      THIS CODE SAMPLE REMAINS WITH YOU.
//  </copyright>
//  <purpose>
//     A class to use when you need to run a command line from C#
//  </purpose>
//  <notes>
//  </notes>
//</filedescription>
//-----------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;



namespace Microsoft.ConfigurationManagement.IntegrationDevelopmentKit
{
    /// <summary>
    /// 
    /// </summary>
    public class RunCommandLine
    {
        //
        // Credit: Microsoft Corporation. "How To Write a Wrapper for a Command-Line Tool with Visual C# .NET"
        // 
        // See http://support.microsoft.com/kb/305994 for more info.
        //
        // Modifications by Robert Hearn:
        //  - moved string return value to an out parameter
        //  - made return value an integer (for quick evaluation of success/failure of the operation)
        //  - added parameter validation
        //  - added parsing of command exit codes
        //  - added workingDirectory parameter
        //
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="exeName"></param>
        /// <param name="workingDirectory"></param>
        /// <param name="argsLine"></param>
        /// <param name="timeoutSeconds"></param>
        /// <param name="stdOutput"></param>
        /// <returns></returns>
        public static int RunExe(string exeName, string workingDirectory, string argsLine, int timeoutSeconds, out string stdOutput)
        {
            stdOutput = string.Empty;

            if (string.IsNullOrEmpty(exeName))
            {
                throw new ArgumentException("RunExe: Parameter 'exeName' was null when calling RunExe");
            }
            if (string.IsNullOrEmpty(argsLine))
            {
                stdOutput += "\n\nRunExe: 'argsLine' parameter was null. Running command line without arguments.\n";
            }
            if (string.IsNullOrEmpty(workingDirectory))
            {
                workingDirectory = Directory.GetCurrentDirectory();
                stdOutput += "\n\nRunExe: 'workingDirectory' parameter was null. Setting the working directory to the current directory, which is " + workingDirectory + "\n";
            }
            if (Directory.Exists(workingDirectory) == false)
            {
                throw new ArgumentException("RunExe: 'workingDirectory' specified (" + workingDirectory + ") does not exist.");
            }
            if (timeoutSeconds < 0)
            {
                stdOutput += "\n\nRunExe: 'timeoutSeconds' parameter was invalid. Setting timeout to 0 (no timeout).\n";
                timeoutSeconds = 0;
            }
            if (File.Exists(exeName) == false)
            {
                throw new ArgumentException("RunExe: File name to run (" + exeName + ") was not found. Cannot continue.");
            }

            bool useShellExecute = false;   //set to true for testing to see a window on the test machine
            Process newProcess = new Process();
            string stdError = string.Empty;

            try
            {
                newProcess.StartInfo.FileName = exeName;
                newProcess.StartInfo.Arguments = argsLine;
                newProcess.StartInfo.WorkingDirectory = workingDirectory;
                
                if (useShellExecute == true)
                {
                    newProcess.StartInfo.UseShellExecute = true;
                    newProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
                    newProcess.StartInfo.CreateNoWindow = true;
                }
                else
                {
                    newProcess.StartInfo.UseShellExecute = false;
                    newProcess.StartInfo.CreateNoWindow = true;
                    newProcess.StartInfo.RedirectStandardOutput = true;
                    newProcess.StartInfo.RedirectStandardError = true;
                    
                }

                stdOutput = "\n\nRunExe: Running command line: " + newProcess.StartInfo.FileName +
                    " " + newProcess.StartInfo.Arguments + "\n";

                

                newProcess.Start();
                stdOutput += "\nRunExe: Process started.\n\n";

                if (newProcess.HasExited == false)
                {
                    newProcess.MaxWorkingSet = (IntPtr)(40 * 1024 * 1024);  // 40 megabytes
                    newProcess.MinWorkingSet = (IntPtr)(20 * 1024 * 1024);  // 20 megabytes
                }

                //stdOutput += "\nSetting MinWorkingSet value to = " + newProcess.MinWorkingSet.ToString() + "\n";
                //stdOutput += "\nSetting MaxWorkingSet value to = " + newProcess.MaxWorkingSet.ToString() + "\n";

                //newProcess.PriorityClass = ProcessPriorityClass.AboveNormal;
                
                if (useShellExecute == false)
                {
                    stdOutput += newProcess.StandardOutput.ReadToEnd(); ;
                    stdError += newProcess.StandardError.ReadToEnd(); ;
                }

                bool success = newProcess.WaitForExit(timeoutSeconds * 1000);
                
                
                if ((success == false) || (newProcess.HasExited == false))
                {
                    stdOutput += "\nRunExe: Program exceeded the extended timeout allowed. Killing the process.\n";
                    newProcess.Kill();
                }

                int exitCode = newProcess.ExitCode;
                stdOutput += "\n\nRunExe: Process ended with exit code [" + exitCode.ToString() + "]\n"; 
                if (exitCode != 0)
                {
                    stdOutput += "\nRunExe: Non-zero exit code. Adding StdError output from process: \n" + stdError + "\n";
                }
                if (newProcess != null)
                {
                    newProcess.Close();
                }


                return exitCode;
            }

            catch (Exception e)
            {
                stdOutput += "\n\nRunExe: An error occurred running " + exeName + ".\n\n" + e.StackTrace + "\n\n" + e.Message + "\n\n" + e.Data + "\n\n";
                return -1;
            }
            finally
            {
                if (newProcess != null)
                {
                    newProcess.Close();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="workingDirectory"></param>
        /// <param name="argsLine"></param>
        /// <param name="timeoutSeconds"></param>
        /// <param name="stdOutput"></param>
        /// <returns></returns>
        public static int RunBat(string fileName, string workingDirectory, string argsLine, int timeoutSeconds, out string stdOutput)
        {
            stdOutput = null;

            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentException("Parameter 'fileName' was null when calling RunBat");
            }
            if (string.IsNullOrEmpty(argsLine))
            {
                stdOutput += "\n'argsLine' parameter was null. Running command line without arguments";
            }
            if (string.IsNullOrEmpty(workingDirectory))
            {
                workingDirectory = Directory.GetCurrentDirectory();
                stdOutput += "\n'workingDirectory' parameter was null. Setting the working directory to the current directory, which is " + workingDirectory;
            }
            if (Directory.Exists(workingDirectory) == false)
            {
                throw new ArgumentException("'workingDirectory' specified (" + workingDirectory + ") does not exist. ");
            }
            if (timeoutSeconds < 0)
            {
                stdOutput += "\n'timeoutSeconds' parameter was invalid. Setting timeout to 0 (no timeout)";
                timeoutSeconds = 0;
            }
            if (File.Exists(fileName) == false)
            {
                throw new ArgumentException("Batch file to run (" + fileName + ") was not found. Cannot continue.");
            }

            try
            {
                int exitCode = RunExe("cmd.exe", workingDirectory, fileName + " " + argsLine, timeoutSeconds, out stdOutput);
                return exitCode;
            }

            catch (Exception e)
            {
                stdOutput += string.Format("An error occurred running " + fileName + ".\nOutput from the process is below:\n" + stdOutput, e);
                return -1;
            }
        }
    }
}
